/**
 * Agrid: "Accessible" grid JQuery plugin
 *
 * Agrid uses existing table markup and attaches new functionality to it.
 *
 * @author Viktor Bolshov <crocodile2u@yandex.ru>
 */

/**
 * the Agrid constructor
 * @param selector element the selector for the root element containing the grid
 */
$.Agrid = function(element) {
	this.element = $(element).get(0);
	this.table = $('tbody', this.element).get(0);
	
	// determine columns
	var cols = {};
	var cols_count = 0;
	$('thead th', this.element).each(function(i){
		var df = this.getAttribute('data-format');
		if (df)
		{
			cols[i] = df;
		}
		cols_count++;
	});
	this.cols = cols;
	this.cols_count = cols_count;
	
	// edit-row dialog
	this.dialog = $('<div/>');
	
	// attach new functions to "edit" and "delete" links
	this.ajaxify(this.element);
	
	// enable sorting
	var that = this;
	$('a.sortable', this.element).each(function(){
		var dir = this.getAttribute('sort-dir');
		if (dir) {
			$(this).addClass('sort-' + dir);
		}
		$(this).click(function(){
			if (! that.listen) {
				return false;
			}
			
			$('a.sortable', that.element).removeClass('sort-asc');
			$('a.sortable', that.element).removeClass('sort-desc');
			
			var dir = this.getAttribute('sort-dir');
			if (dir && ('asc' == dir)) {
				dir = 'desc';
			} else {
				dir = 'asc';
			}
			this.setAttribute('sort-dir', dir);
			$(this).addClass('sort-' + dir);
			that.load(this.getAttribute('sort-' + dir));
			return false;
		});
	});
	
	// we're about to listen to incoming events
	this.listen = true;
}

/**
 * Instance methods
 */
$.Agrid.prototype = {
	// open edit dialog
	// @param HTMLLinkElement a
	startEdit: function(a) {
		if (! this.listen) {
			return;
		}
		
		// the scripts in the dialog window
		// will interact with the grid via this property
		$.Agrid.activeInstance = this;
		
		if ($(a).hasClass('new'))
		{
			this.row = null;
		} else {
			this.row = this.findRow(a);
		}
		var that = this;
		this.dialog.dialog({
			autoOpen: true,
			modal: true,
			title: a.title || a.innerHTML,
			width: 600,// @todo make size configurable
			height: 400
		}).load(a.href);
		return false;
	},
	// start delete, ask for confirmation if the user wants us to
	// @param HTMLLinkElement a
	startDelete: function(a) {
		if (! this.listen) {
			return;
		}

		if ((! a.title) || confirm(a.title))
		{
			var row = this.findRow(a);
			if (row)
			{
				$.get(a.href, function(response){
					var data = response.json();
					if (data.success > 0)
					{
						row.parentNode.removeChild(row);
					} else {
						alert(data.message || "delete failed");
					}
				}, "json");
			} else {
				alert("Agrid cannot find the row to delete");
			}
		}
	},
	// load rows in JSON format from the url
	// @param string url
	load: function(url) {
		if (! this.listen) {
			return;
		}
		this.listen = false;
		this.element.style.opacity = '0.5';
		var that = this;
		$.get(url, function(response){
			that.loadRows(response.json());
			that.listen = true;
			that.element.style.opacity = '1';
		});
	},
	// load rows in rows array into the grid
	// @param object[] rows
	loadRows: function(rows) {
		this.clear();
		for (var i in rows) {
			this.renderRow(rows[i]);
		}
	},
	// close the editor, update the row that's being edited or insert a new one
	// @param object data
	closeEditor: function(data) {
		this.renderRow(data, this.row);
		this.row = null;
		this.dialog.dialog("close");
	},
	// update the row that's being edited or insert a new one
	// @param object data
	// @param HTMLTableRowElement existing_row (optional)
	renderRow: function (data, existing_row) {
		var row;
		if (existing_row)
		{
			row = existing_row;
		} else {
			row = this.table.appendChild(document.createElement('tr'));
			for (var i = 0; i < this.cols_count; ++i) {
				row.appendChild(document.createElement('td'));
			}
		}
		var that = this;
		$('td', row).each(function(i){
			if (i in that.cols)
			{
				this.innerHTML = that.cols[i].format(data);
			}
		});
		this.ajaxify(row);
	},
	// bring new functionality to hyperlinks inside parent element
	// @param DOMNode parent
	ajaxify: function(parent) {
		var that = this;
		$('a.edit', parent).each(function() {
			$(this).click(function(){
				that.startEdit(this);
				return false;
			});
		});
		$('a.delete', parent).each(function() {
			$(this).click(function(){
				that.startDelete(this);
				return false;
			});
		});
	},
	// find a table row being given its child node of an arbitrary level
	// @param DOMNode child
	// @return HTMLTableRowElement or NULL
	findRow: function(child) {
		var tr = child;
		while (tr.tagName) {
			if ('tr' == tr.tagName.toLowerCase())
			{
				break;
			}
			tr = tr.parentNode;
		}
		return ('tr' == tr.tagName.toLowerCase()) ? tr : null;
	},
	// clear existing rows
	clear: function()
	{
		while (this.table.hasChildNodes())
		{
			this.table.removeChild(this.table.childNodes.item(0));
		}
	}
}

// helper functions:

// a small template engine
String.prototype.format = function(data) {
	var that = this;
	for (var i in data) {
		var re = new RegExp("\{"+i+"\}");
		while (that.match(re)) that = that.replace(re, data[i]);
	}
	return that;
}
// get JSON-encoded variable
String.prototype.json = function() {
	try {
		eval('var json = (' + this + ')');
		return json;
	} catch (e) {
		alert("Error decoding JSON: "+e.message);
	}
}
